package cn.tedu.mall.order.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.pojo.domain.CsmallAuthenticationInfo;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.order.mapper.OmsOrderItemMapper;
import cn.tedu.mall.order.mapper.OmsOrderMapper;
import cn.tedu.mall.order.service.IOmsCartService;
import cn.tedu.mall.order.service.IOmsOrderService;
import cn.tedu.mall.order.utils.IdGeneratorUtils;
import cn.tedu.mall.pojo.order.dto.OrderAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderItemAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderListTimeDTO;
import cn.tedu.mall.pojo.order.dto.OrderStateUpdateDTO;
import cn.tedu.mall.pojo.order.model.OmsCart;
import cn.tedu.mall.pojo.order.model.OmsOrder;
import cn.tedu.mall.pojo.order.model.OmsOrderItem;
import cn.tedu.mall.pojo.order.vo.OrderAddVO;
import cn.tedu.mall.pojo.order.vo.OrderDetailVO;
import cn.tedu.mall.pojo.order.vo.OrderListVO;
import cn.tedu.mall.product.service.order.IForOrderSkuService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

// 后面的秒杀业务需要调用这个生成订单的方法,所以需要支持dubbo调用
@DubboService
@Service
@Slf4j
public class OmsOrderServiceImpl implements IOmsOrderService {

    @DubboReference
    private IForOrderSkuService dubboSkuService;
    @Autowired
    private IOmsCartService omsCartService;
    @Autowired
    private OmsOrderMapper omsOrderMapper;
    @Autowired
    private OmsOrderItemMapper omsOrderItemMapper;

    // 新增订单的方法
    // 这个方法dubbo调用了Product模块的方法,操作了数据库,有分布式的事务需求
    // 所以要使用注解激活Seata分布式事务的功能
    @GlobalTransactional
    @Override
    public OrderAddVO addOrder(OrderAddDTO orderAddDTO) {

        // 第一部分:收集信息,准备数据
        OmsOrder order = new OmsOrder();
        BeanUtils.copyProperties(orderAddDTO,order);

        // 为Order对象补全属性值的方法
        loadOrder(order);
        // 到此为止,order的普通属性全部赋值完毕

        // 下面要将参数orderAddDTO中包含的订单项(orderItem集合)信息赋值
        // 首先取出这个集合,也就是当前订单中包含的所有商品的集合
        //TODO 为什么会有订单项，方法参数orderAddDTO从哪里来的
        List<OrderItemAddDTO> itemAddDTOS = orderAddDTO.getOrderItems();

        if(itemAddDTOS == null || itemAddDTOS.isEmpty()){
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"至少有一件商品");
        }

        // 将当前订单中包含的订单项新增到数据库
        // 当前集合泛型是OrderItemAddDTO,我们编写的新增到数据库的方法泛型是OmsOrderItem
        // 将上集合转换为List<OmsOrderItem>集合
        List<OmsOrderItem> orderItems = new ArrayList<>();
        for (OrderItemAddDTO itemAddDTO : itemAddDTOS) {
            OmsOrderItem orderItem = new OmsOrderItem();
            BeanUtils.copyProperties(itemAddDTO,orderItem);
            Long itemId = IdGeneratorUtils.getDistributeId("order_item");

            // 将addDTO对象中没有的id和orderId属性赋值
            orderItem.setId(itemId);
            orderItem.setOrderId(order.getId());
            orderItems.add(orderItem);

            // 第二部分:执行操作数据库的指令
            // 1.减少库存
            // 当前循环是订单中的一件商品,我们可以在此处对这个商品进行库存的减少
            // 当前对象属性中是包含skuId和要购买的商品数量的,所以可以执行库存的修改
            // 先获取skuId
            Long skuId = orderItem.getSkuId();
            int rows = dubboSkuService.reduceStockNum(skuId, orderItem.getQuantity());
            if(rows == 0){
                log.warn("商品库存不足,skuId:{}",skuId);
                throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"库存不足");
            }
            // 2.删除勾选的购物车商品
            OmsCart omsCart = new OmsCart();
            omsCart.setSkuId(skuId);
            omsCart.setUserId(order.getUserId());
            omsCartService.removeUserCarts(omsCart);
        }
        // 3.执行新增订单
        omsOrderMapper.insertOrder(order);

        // 4.新增订单中所有商品的订单项信息
        omsOrderItemMapper.insertOrderItemList(orderItems);

        // 第三部分:准备返回值,返回给前端
        // 当前业务逻辑层方法返回值为OrderAddVO
        // 我们需要做的就是实例化这个对象,给它赋值并返回
        OrderAddVO orderAddVO = new OrderAddVO();
        orderAddVO.setId(order.getId());
        orderAddVO.setSn(order.getSn());
        orderAddVO.setPayAmount(order.getAmountOfActualPay());
        orderAddVO.setCreateTime(order.getGmtOrder());

        return orderAddVO;
    }

    // 为Order对象补全属性值的方法
    private void loadOrder(OmsOrder order) {

        // 本方法针对order对象没有被赋值的属性,进行生成或手动赋值
        // 给id赋值,订单业务不使用数据库自增列做id,而使用Leaf分布式序列生成系统
        Long id = IdGeneratorUtils.getDistributeId("order");
        order.setId(id);

        // 生成订单号,直接使用UUID即可
        order.setSn(UUID.randomUUID().toString());

        // 赋值UserId
        // 以后秒杀业务调用这个方法时,userId属性是会被赋值的
        // 所以这里要判断一下userId是否已经有值,没有值再赋值
        if(order.getUserId() == null){
            order.setUserId(getUserId());
        }

        // 为订单状态赋值
        // 订单状态如果为null ,将其设默认值0,表示未支付
        if(order.getState() == null){
            order.setState(0);
        }

        // 为了保证下单时间、数据创建时间和最后修改时间一致
        // 我们给他们赋相同的值
        LocalDateTime now = LocalDateTime.now();
        order.setGmtOrder(now);
        order.setGmtCreate(now);
        order.setGmtModified(now);

        //价格
        BigDecimal price = order.getAmountOfOriginalPrice();
        //运费
        BigDecimal freight = order.getAmountOfFreight();
        //优惠
        BigDecimal discount = order.getAmountOfDiscount();
        // 验算实际支付金额
        // 计算公式:   实际支付金额=原价-优惠+运费
        // 数据类型使用BigDecimal,防止浮点偏移,还有更大的取值范围
        BigDecimal actualPay = price.subtract(discount).add(freight);
        order.setAmountOfActualPay(actualPay);
    }

    // 根据订单id,修改订单的状态
    @Override
    public void updateOrderState(OrderStateUpdateDTO orderStateUpdateDTO) {
        OmsOrder order = new OmsOrder();
        BeanUtils.copyProperties(orderStateUpdateDTO,order);
        int rows = omsOrderMapper.updateOrderById(order);
        if(rows != 1){
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"修改失败");
        }
    }

    // 分页和查询当前登录用户,指定时间范围内所有订单
    // 默认查询最近一个月的订单信息,查询的返回值OrderListVO,是包含订单信息和订单中商品信息的对象
    // 查询依赖了持久层编写好的映射关系,xml文件编写的关联查询!
    @Override
    public JsonPage<OrderListVO> listOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO) {
        // 业务逻辑层首先要判断给定的时间范围,如果为空默认最近一个月
        // 如果不为空要保证结束时间大于开始时间,可以编写一个方法专门判断
        validateTimeAndLoadTime(orderListTimeDTO);
        // 将userId赋值到参数中
        orderListTimeDTO.setUserId(getUserId());
        // 分页查询要设置分页条件
        PageHelper.startPage(orderListTimeDTO.getPage(),orderListTimeDTO.getPageSize());
        // 调用关联查询的方法,获得包含订单中商品信息的订单集合
        List<OrderListVO> orderList = omsOrderMapper.selectOrdersBetweenTimes(orderListTimeDTO);
        return JsonPage.restPage(new PageInfo<>(orderList));
    }

    private void validateTimeAndLoadTime(OrderListTimeDTO orderListTimeDTO) {
        // 获取开始时间和结束时间
        LocalDateTime start = orderListTimeDTO.getStartTime();
        LocalDateTime endTime = orderListTimeDTO.getEndTime();
        // 为了让我们的业务更加简单明了,我们设计start和end任意一个为空就查询最近一个月的订单
        if(start == null || endTime == null){
            // start设置为一个月之前的时间
            start = LocalDateTime.now().minusMonths(1);
            endTime = LocalDateTime.now();
            orderListTimeDTO.setStartTime(start);
            orderListTimeDTO.setEndTime(endTime);
        }else {
            // 如果start和end都非null
            // 就要判断start是否小于end,否则要抛出异常 ,也可以使用isAfter方法判断
            // 现在的时区转为格林威治时间，进行比较
            if(endTime.toInstant(ZoneOffset.of("+8")).toEpochMilli() <
                    start.toInstant(ZoneOffset.of("+8")).toEpochMilli()){
                throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"结束时间应大于开始时间");
            }
        }
    }

    @Override
    public OrderDetailVO getOrderDetail(Long id) {
        return null;
    }

    // 业务逻辑层中有获得当前登录用户信息的需求
    // 我们的项目会在控制器方法运行前运行的过滤器中,解析前端传入的JWT
    // 将解析获得的用户信息保存在SpringSecurity上下文中
    // 这里可以编写方法从SpringSecurity上下文中获得用户信息
    public CsmallAuthenticationInfo getUserInfo(){
        // 编写SpringSecurity上下文中获得用户信息的代码
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();

        // 为了逻辑严谨性,判断一下SpringSecurity上下文中的信息是不是null
        if(authenticationToken == null){
            String message = "您没有登录！！！！";
            log.warn(message);
            throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,message);
        }

        // 确定authenticationToken不为null
        // 就可以从中获得用户信息了
        CsmallAuthenticationInfo csmallAuthenticationInfo = (CsmallAuthenticationInfo) authenticationToken.getCredentials();
        return csmallAuthenticationInfo;
    }

    /*获取用户id*/
    public Long getUserId(){
        return getUserInfo().getId();
    }
}
